/*
 * @[H]:  Copyright (c) 2021 Phytium Information Technology, Inc. 
 * 
 *  SPDX-License-Identifier: Apache-2.0. 
 * 
 * @Date: 2021-07-27 14:42:08
 * @LastEditTime: 2021-07-28 18:00:24
 * @Description:  Description of file
 * @Modify History: 
 * * * Ver   Who        Date         Changes
 * * ----- ------     --------    --------------------------------------
 */
#include "ft_can.h"
#include "ft_debug.h"
#include "ft_assert.h"
#include "parameters.h"
#include "string.h"
#include <limits.h>
#include <stdlib.h>
#include "kernel.h"

typedef struct
{
    u32 tseg1_min; /* Time segement 1 = prop_seg + phase_seg1 */
    u32 tseg1_max;
    u32 tseg2_min; /* Time segement 2 = phase_seg2 */
    u32 tseg2_max;
    u32 sjw_max; /* Synchronisation jump width */
    u32 brp_min; /* Bit-rate prescaler */
    u32 brp_max;
    u32 brp_inc;
} FCanBittimingConst;

static const FCanBittimingConst FCanArbBitConst =
    {
        .tseg1_min = FCAN_ARB_TSEG1_MIN, /* Time segement 1 = prop_seg + phase_seg1 */
        .tseg1_max = FCAN_ARB_TSEG1_MAX,
        .tseg2_min = FCAN_ARB_TSEG2_MIN, /* Time segement 2 = phase_seg2 */
        .tseg2_max = FCAN_ARB_TSEG2_MAX,
        .sjw_max = FCAN_ARB_SJW_MAX, /* Synchronisation jump width */
        .brp_min = FCAN_ARB_BRP_MIN, /* Bit-rate prescaler */
        .brp_max = FCAN_ARB_BRP_MAX,
        .brp_inc = FCAN_ARB_BRP_INC,
};

static const FCanBittimingConst FCanDataBitConst =
    {
        .tseg1_min = FCAN_DATA_TSEG1_MIN, /* Time segement 1 = prop_seg + phase_seg1 */
        .tseg1_max = FCAN_DATA_TSEG1_MAX,
        .tseg2_min = FCAN_DATA_TSEG2_MIN, /* Time segement 2 = phase_seg2 */
        .tseg2_max = FCAN_DATA_TSEG2_MAX,
        .sjw_max = FCAN_DATA_SJW_MAX, /* Synchronisation jump width */
        .brp_min = FCAN_DATA_BRP_MIN, /* Bit-rate prescaler */
        .brp_max = FCAN_DATA_BRP_MAX,
        .brp_inc = FCAN_DATA_BRP_INC,
};

static s32 FCanUpdateSamplePoint(const FCanBittimingConst *btc,
                                 u32 sample_point_nominal, u32 tseg,
                                 u32 *tseg1_ptr, u32 *tseg2_ptr,
                                 u32 *sample_point_error_ptr)
{
    u32 sample_point_error, best_sample_point_error = UINT_MAX;
    u32 sample_point, best_sample_point = 0;
    u32 tseg1, tseg2;
    s32 i;

    for (i = 0; i <= 1; i++)
    {
        tseg2 = tseg + CAN_CALC_SYNC_SEG - (sample_point_nominal * (tseg + CAN_CALC_SYNC_SEG)) / 1000 - i;
        tseg2 = clamp(tseg2, btc->tseg2_min, btc->tseg2_max);
        tseg1 = tseg - tseg2;
        if (tseg1 > btc->tseg1_max)
        {
            tseg1 = btc->tseg1_max;
            tseg2 = tseg - tseg1;
        }

        sample_point = 1000 * (tseg + CAN_CALC_SYNC_SEG - tseg2) / (tseg + CAN_CALC_SYNC_SEG);
        sample_point_error = abs(sample_point_nominal - sample_point);

        if ((sample_point <= sample_point_nominal) && (sample_point_error < best_sample_point_error))
        {
            best_sample_point = sample_point;
            best_sample_point_error = sample_point_error;
            *tseg1_ptr = tseg1;
            *tseg2_ptr = tseg2;
        }
    }

    if (sample_point_error_ptr)
        *sample_point_error_ptr = best_sample_point_error;

    return best_sample_point;
}

ft_error_t FCanCalcBittiming(struct FCanBittiming *bt_p, u32 TimingTagetSelect)
{
    u32 bitrate;       /* current bitrate */
    u32 bitrate_error; /* difference between current and nominal value */
    u32 best_bitrate_error = UINT_MAX;
    u32 sample_point_error; /* difference between current and nominal value */
    u32 best_sample_point_error = UINT_MAX;
    u32 sample_point_nominal; /* nominal sample point */
    u32 best_tseg = 0;        /* current best value for tseg */
    u32 best_brp = 0;         /* current best value for brp */
    u32 brp, tsegall, tseg, tseg1 = 0, tseg2 = 0;
    u64 v64;

    const FCanBittimingConst *btc;
    struct FCanBittiming *bt = bt_p;
    FT_ASSERTZERONUM(bt_p != NULL);
    FT_ASSERTZERONUM(TimingTagetSelect <= FCAN_CALC_DATA_TIMING);

    if (TimingTagetSelect == FCAN_CALC_DATA_TIMING)
    {
        btc = &FCanArbBitConst;
    }
    else
    {
        btc = &FCanDataBitConst;
    }

    if (bt->sample_point)
    {
        sample_point_nominal = bt->sample_point;
    }
    else
    {
        if (bt->bitrate > 800000)
            sample_point_nominal = 750;
        else if (bt->bitrate > 500000)
            sample_point_nominal = 800;
        else
            sample_point_nominal = 875;
    }

    for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1;
         tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--)
    {
        tsegall = CAN_CALC_SYNC_SEG + tseg / 2;

        /* Compute all possible tseg choices (tseg=tseg1+tseg2) */
        brp = FCAN_REF_CLOCK / (tsegall * bt->bitrate) + tseg % 2;

        /* choose brp step which is possible in system */
        brp = (brp / btc->brp_inc) * btc->brp_inc;

        if ((brp < btc->brp_min) || (brp > btc->brp_max))
            continue;

        bitrate = FCAN_REF_CLOCK / (brp * tsegall);

        bitrate_error = abs(bt->bitrate - bitrate);
        /* tseg brp biterror */
        if (bitrate_error > best_bitrate_error)
            continue;

        /* reset sample point error if we have a better bitrate */
        if (bitrate_error < best_bitrate_error)
            best_sample_point_error = UINT_MAX;

        FCanUpdateSamplePoint(btc, sample_point_nominal, tseg / 2, &tseg1, &tseg2, &sample_point_error);
        if (sample_point_error > best_sample_point_error)
            continue;

        best_sample_point_error = sample_point_error;
        best_bitrate_error = bitrate_error;
        best_tseg = tseg / 2;
        best_brp = brp;

        if (bitrate_error == 0 && sample_point_error == 0)
            break;
    }

    if (best_bitrate_error)
    {
        /* Error in one-tenth of a percent */
        v64 = (u64)best_bitrate_error * 1000;
        do_div(v64, bt->bitrate);
        bitrate_error = (u32)v64;
        if (bitrate_error > CAN_CALC_MAX_ERROR)
        {
            printf("bitrate error");
        }
        return FCAN_FAILURE;
        printf("bitrate error 2");
    }

    /* real sample point */
    bt->sample_point = FCanUpdateSamplePoint(btc, sample_point_nominal, best_tseg,
                                             &tseg1, &tseg2, NULL);

    v64 = (u64)best_brp * 1000 * 1000 * 1000;
    do_div(v64, FCAN_REF_CLOCK);
    bt->tq = (u64)v64;
    bt->prop_seg = tseg1 / 2;
    bt->phase_seg1 = tseg1 - bt->prop_seg;
    bt->phase_seg2 = tseg2;

    /* check for sjw user settings */
    if (!bt->sjw || !btc->sjw_max)
    {
        bt->sjw = 1;
    }
    else
    {
        /* bt->sjw is at least 1 -> sanitize upper bound to sjw_max */
        if (bt->sjw > btc->sjw_max)
            bt->sjw = btc->sjw_max;
        /* bt->sjw must not be higher than tseg2 */
        if (tseg2 < bt->sjw)
            bt->sjw = tseg2;
    }

    bt->brp = best_brp;

    /* real bitrate */
    bt->bitrate = FCAN_REF_CLOCK / (bt->brp * (CAN_CALC_SYNC_SEG + tseg1 + tseg2));
    return FCAN_SUCCESS;
}
